/*
 * CCasterChannel.cpp
 *
 *  Created on: 2016年4月6日
 *      Author: terry
 */

#include "CCasterChannel.h"
#include "H264NaluParser.h"
#include "CLog.h"
#include "StopWatch.h"


namespace av
{

CCasterChannel::CCasterChannel():
    m_videoLastPts(),
    m_audioLastPts()
{
}

CCasterChannel::~CCasterChannel()
{
}

int CCasterChannel::open(const char* name, const MediaFormat& fmt)
{
    if (!name || strlen(name) <= 0)
    {
        return EINVAL;
    }

    if (!fmt.isValid())
    {
        return EINVAL;
    }

    m_url = name;
    m_format = fmt;

    if (m_format.m_clockRate <= 0)
    {
        m_format.m_clockRate = 90000;
    }

    if (m_format.m_audioRate <= 0)
    {
        m_format.m_audioRate = m_format.m_sampleRate;
    }

	if (m_format.m_bitrate <= 0)
	{
		m_format.m_bitrate = m_format.m_width * m_format.m_height / (352 * 288) * 100000;
	}

	if (m_format.m_audioBitrate <= 0)
	{
		m_format.m_audioBitrate = m_format.m_channels * m_format.m_sampleRate * 2;
		if ((m_format.m_audioCodec == MEDIA_CODEC_G711A) || (m_format.m_audioCodec == MEDIA_CODEC_G711U))
		{
			//
		}
		else
		{
			m_format.m_audioBitrate = m_format.m_audioBitrate / 8;
		}
	}

    return 0;
}

int CCasterChannel::open(const std::string& url, const std::string& params)
{
	m_url = url;

	return 0;
}

void CCasterChannel::close()
{
	m_url.clear();
}

bool CCasterChannel::isOpen()
{
	return (!m_url.empty());
}

std::string CCasterChannel::getName()
{
	return m_url;
}

bool CCasterChannel::setFormat(const MediaFormat& fmt)
{
    m_format = fmt;
    return true;
}

int CCasterChannel::write(MediaPacketPtr& pkt)
{
    if (!pkt)
    {
        return EINVAL;
    }

    comn::StopWatch watch;

    correctDuration(*pkt.get());

    if (pkt->isVideo() && m_format.m_videoProp.empty())
    {
        parseParamSet(*pkt.get());
    }

    fireMediaPacket(pkt);

    if (watch.elapse() > 50)
    {
        CLog::warning("CCasterChannel::write consume too much time. type:%d, elapse:%d\n", pkt->type, (int)watch.elapse());
    }

    return 0;
}

void CCasterChannel::setVideoProp(const uint8_t* prop, int size)
{
    if (!prop || size <= 0)
    {
        m_format.m_videoProp.clear();
    }
    else
    {
        m_format.m_videoProp.assign((const char*)prop, size);
    }
}

void CCasterChannel::parseParamSet(MediaPacket& pkt)
{
    if (m_format.m_codec == MEDIA_CODEC_H264)
    {
        parseH264ParamSet(pkt);
    }
    else if (m_format.m_codec == MEDIA_CODEC_HEVC)
    {
        parseH265ParamSet(pkt);
    }
}

bool CCasterChannel::parseH264ParamSet(MediaPacket& pkt)
{
    if (!H264NaluParser::startWithH264Code(pkt.data, pkt.size))
    {
        return false;
    }
    
    uint8_t* data = pkt.data;
    size_t size = pkt.size;

    size_t begin = 0;
    bool found = true;

    while (found)
    {
        size_t pos = H264NaluParser::findH264StartCode(data, size, begin + 3);
        if (pos != -1)
        {
            parseH264Nalu(data + begin, pos - begin);
            begin = pos;
        }
        else
        {
            break;
        }
    }

    parseH264Nalu(data + begin, size - begin);

    if (m_sps.size() && m_pps.size())
    {
        std::string prop;
        prop.append((char*)H264NaluParser::s_startCode, H264NaluParser::START_CODE_LENGTH);
        prop.append(m_sps);
        prop.append((char*)H264NaluParser::s_startCode, H264NaluParser::START_CODE_LENGTH);
        prop.append(m_pps);

        m_format.m_videoProp = prop;

        return true;
    }

    return false;
}

bool CCasterChannel::parseH265ParamSet(MediaPacket& pkt)
{
    if (!H264NaluParser::startWithH264Code(pkt.data, pkt.size))
    {
        return false;
    }

    uint8_t* data = pkt.data;
    size_t size = pkt.size;

    size_t begin = 0;
    bool found = true;

    while (found)
    {
        size_t pos = H264NaluParser::findH264StartCode(data, size, begin + 3);
        if (pos != -1)
        {
            parseH265Nalu(data + begin, pos - begin);
            begin = pos;
        }
        else
        {
            break;
        }
    }

    parseH265Nalu(data + begin, size - begin);

    if (m_sps.size() && m_pps.size())
    {
        std::string prop;
        prop.append((char*)H264NaluParser::s_startCode, H264NaluParser::START_CODE_LENGTH);
        prop.append(m_vps);
        prop.append((char*)H264NaluParser::s_startCode, H264NaluParser::START_CODE_LENGTH);
        prop.append(m_sps);
        prop.append((char*)H264NaluParser::s_startCode, H264NaluParser::START_CODE_LENGTH);
        prop.append(m_pps);

        m_format.m_videoProp = prop;

        return true;
    }

    return false;
}

void CCasterChannel::parseH264Nalu(const uint8_t* data, size_t length)
{
    NaluPacket nalu;
    if (!H264NaluParser::parseNalu(data, length, nalu))
    {
        return ;
    }

    if (nalu.type == NaluPacket::NALU_SPS)
    {
        m_sps.assign((char*)nalu.data, nalu.length);
    }
    else if (nalu.type == NaluPacket::NALU_PPS)
    {
        m_pps.assign((char*)nalu.data, nalu.length);
    }
}

void CCasterChannel::parseH265Nalu(const uint8_t* data, size_t length)
{
    NaluPacket nalu;
    if (!H264NaluParser::parseNalu(data, length, nalu))
    {
        return ;
    }

    nalu.type = (nalu.data[0] & 0x7E) >> 1;
    if (nalu.type == 32)
    {
        m_vps.assign((char*)nalu.data, nalu.length);
    }
    else if (nalu.type == 33)
    {
        m_sps.assign((char*)nalu.data, nalu.length);
    }
    else if (nalu.type == 34)
    {
        m_pps.assign((char*)nalu.data, nalu.length);
    }
}

void CCasterChannel::correctDuration(MediaPacket& pkt)
{
    if (pkt.isVideo())
    {
        if (m_videoLastPts == 0)
        {
            m_videoLastPts = pkt.pts;
        }

        if (pkt.duration <= 0)
        {
            pkt.duration = (int)(pkt.pts - m_videoLastPts);
        }

        m_videoLastPts = pkt.pts;
    }
    else
    {
        if (m_audioLastPts == 0)
        {
            m_audioLastPts = pkt.pts;
        }

        if (pkt.duration <= 0)
        {
            pkt.duration = (int)(pkt.pts - m_audioLastPts);
        }

        m_audioLastPts = pkt.pts;
    }

}


} /* namespace av */
